const utils = require('../utils')
const path = require('path')
const dict_config_keys = require('../dict_config_keys')
const fs = require('fs')
const packageInfo = require('../../package.json')

const FISHDICT_FILE_ID = '__FISHDICT__'

class BaseDictMaker {
    /**
     * 
     * @param {*} header 
     * @param {*} dict_folder 
     * @param {*} dst_folder 
     * @param {*} options {debug}
     */
    constructor(header, dict_folder, dst_folder, options) {
        this.header_ = header;
        this.dict_folder_ = dict_folder;
        this.dst_folder_ = dst_folder;
        this.options_ = options
    }

    isDebugMode() {
        return false;
        return this.options_ && this.options_.debug;
    }

    async make() {
        if (this.isDebugMode()) {
            this.header_ && this.header_.parse();
        }
        const words = await this.parse();
        this.makeCore(words)
        return await this.makeDictFile();
    }

    /**
     * @description: need override
     * @returns words, [{index:1, word:我 content:mdcontent}]
     */
    async parse() {

    }

    handleReplace(words, replace_path) {
        let str = fs.readFileSync(replace_path, { encoding: 'utf-8' })
        str = str.replace(/\r\n/g, '\n')
        const arr = str.split('\n')
        const to_replace = []
        arr && arr.forEach((line) => {
            const pos = line.indexOf(' ')
            if (pos >= 0) {
                const key = line.substring(0, pos)
                const value = line.substring(pos + 1).trim()
                if (value) to_replace.push({ key, value })
            }
        })

        words.forEach((item) => {
            item.content = this.replaceContent(item.content, to_replace);
        })
    }

    replaceContent(content, to_replace) {
        let r = content
        to_replace.forEach((t) => {
            r = r.replaceAll(t.value, t.key)
        })
        // if (r.indexOf('<f') >=0 || r.indexOf('<b') >= 0 || r.indexOf('<B') >= 0) console.log('...r', r)
        return r;
    }

    makeIndexAndContentFile(words) {
        const contentFile = fs.openSync(path.join(this.getCacheDir(), 'resource', 'content.data'), 'w')
        if (!contentFile) return null;

        const indexFile = fs.openSync(path.join(this.getCacheDir(), 'resource', 'index.data'), 'w')
        if (!indexFile) return null;

        const charset = this.header_.config_[dict_config_keys.charset] || 'utf-8'
        words.forEach((wordItem) => {
            const buf = Buffer.from(wordItem.content, charset);
            fs.writeSync(contentFile, buf);

            const wordBuf = Buffer.from(wordItem.word, charset)
            const indexLen = wordBuf.length + 4;
            const indexLenBuf = Buffer.alloc(2);
            indexLenBuf.writeUint16LE(indexLen)
            fs.writeSync(indexFile, indexLenBuf)

            const lenBuf = Buffer.alloc(4)
            lenBuf.writeUint32LE(buf.length)
            fs.writeSync(indexFile, lenBuf)
            fs.writeSync(indexFile, wordBuf)
        })

        fs.closeSync(contentFile);

        return true;
    }

    wait(ms) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve();
            }, ms)
        })
    }

    makeCore(words) {
        // replace.txt
        const replacePath = path.join(this.dict_folder_, 'resource', 'replace.txt')
        const isReplaceExist = fs.existsSync(replacePath);
        if (isReplaceExist) {
            this.handleReplace(words, replacePath);
        }

        const cacheResourceDir = path.join(this.getCacheDir(), 'resource');
        if (!fs.existsSync(cacheResourceDir)) {
            fs.mkdirSync(cacheResourceDir, { recursive: true })
        }

        fs.writeFileSync(path.join(this.getCacheDir(), 'resource', 'config.json'), this.header_.getJsonString(), { encoding: 'utf-8' })


        // index.data
        // content.data
        if (!this.makeIndexAndContentFile(words)) return null;
    }

    async getResourceOrAssetsFiles(dirName) {
        const thePath = path.join(this.dict_folder_, dirName);
        const arr = []
        if (fs.existsSync(thePath)) {
            await this.getResourceOrAssetsFilesCore(arr, thePath, thePath, dirName);
        }
        return arr;
    }

    async getResourceOrAssetsFilesCore(arr, dir, base, dirName) {
        if (this.isDebugMode()) return;

        const files = fs.readdirSync(dir);
        for (let i = 0; files && i < files.length; ++i) {
            const item = files[i]
            let fullItemPath = path.join(dir, item)
            const fileStat = fs.statSync(fullItemPath);
            if (fileStat.isDirectory()) {
                await this.getResourceOrAssetsFilesCore(arr, fullItemPath, base, dirName);
                continue;
            }
            let fileName = path.join(dirName, fullItemPath.substring(base.length)).replaceAll('\\', '/')
            fileName = fileName.toLowerCase();
            if (fileName.indexOf('.ds_store') >= 0) {
                console.log('...ignore', fileName)
                continue;
            }

            const content = await utils.zipBuffer(fs.readFileSync(fullItemPath))


            const sizeBuf = Buffer.alloc(4)
            sizeBuf.writeUint32LE(content.length)
            fileName = this.getCoreName(fileName);
            arr.push({
                name: fileName,
                content: content,
                size: content.length,

                nameBuf: Buffer.from(fileName, 'utf-8'),
                sizeBuf: sizeBuf,
            })
        }
    }

    getCoreName(name) {
        if (!name.startsWith('resource/') && !name.startsWith('assets/')) {
            throw "..name not start with resource/ or assets/"
        }
        const namePos = name.indexOf('/')
        return name.substring(namePos + 1)
    }

    async getMakeFileInfo(filename, mainDir) {
        let thePath = path.join(!mainDir ? this.getCacheDir() : this.dict_folder_, filename);
        if (!fs.existsSync(thePath)) {
            return {
                name: filename,
                content: null,
                size: 0,
            }
        }
        thePath = thePath.toLowerCase();
        const orgData = fs.readFileSync(thePath);
        const content = await utils.zipBuffer(orgData)
        const sizeBuf = Buffer.alloc(4)
        sizeBuf.writeUint32LE(content.length)

        if (filename == 'resource/content.data') {
            console.log(`..content.size: ${(orgData.length / 1024.0 / 1024.0).toFixed(2)}M`)
        }

        let coreName = mainDir ? path.join('resource', filename) : filename
        coreName = this.getCoreName(coreName);
        return {
            name: coreName,
            content: content,
            size: content.length,

            nameBuf: Buffer.from(coreName, 'utf-8'),
            sizeBuf: sizeBuf
        }
    }

    async makeResourceOrAssetsBuffer(isResource) {
        const allFiles = await this.getResourceOrAssetsFiles(isResource ? 'resource' : 'assets')
        if (isResource) {
            allFiles.push(await this.getMakeFileInfo('about.html', true))
            allFiles.push(await this.getMakeFileInfo('catalog.html', true))
            allFiles.push(await this.getMakeFileInfo('resource/config.json'))
            allFiles.push(await this.getMakeFileInfo('resource/index.data'))
            allFiles.push(await this.getMakeFileInfo('resource/content.data'))
        }


        let justHeaderLen = 0;
        let contentLen = 0;
        allFiles.forEach((item) => {
            if (item.size) {
                justHeaderLen += 4 + 2 + item.nameBuf.length
                contentLen += item.size
            }
        })

        const headerBuf = Buffer.alloc(justHeaderLen)
        let pos = 0;
        allFiles.forEach((item) => {
            if (item.size) {
                headerBuf.writeUint16LE(item.nameBuf.length + 4, pos)
                pos += 2

                item.sizeBuf.copy(headerBuf, pos);
                pos += item.sizeBuf.length;
                item.nameBuf.copy(headerBuf, pos)
                pos += item.nameBuf.length
            }
        })
        const zippedBuf = await utils.zipBuffer(headerBuf)

        pos = 0;
        const allBuf = Buffer.alloc(contentLen + zippedBuf.length + 4)
        allBuf.writeUint32LE(zippedBuf.length, pos);
        pos += 4;
        zippedBuf.copy(allBuf, pos)
        pos += zippedBuf.length
        allFiles.forEach((item) => {
            if (item.size) {
                item.content.copy(allBuf, pos)
                pos += item.size
            }
        })

        return allBuf
    }

    async makeDictFile() {
        const destFilePath = this.getOutputFilePath();
        const hFile = fs.openSync(destFilePath, 'w');
        fs.writeSync(hFile, Buffer.from(FISHDICT_FILE_ID, 'utf-8'));
        const versionBuffer = Buffer.alloc(4)
        versionBuffer.writeUInt32LE(Number(packageInfo.dict_version))
        fs.writeSync(hFile, versionBuffer)

        const resourceBuf = await this.makeResourceOrAssetsBuffer(true);
        const assertsBuf = await this.makeResourceOrAssetsBuffer();

        fs.writeFileSync(hFile, resourceBuf);
        fs.writeFileSync(hFile, assertsBuf)


        fs.closeSync(hFile);
        return destFilePath;
    }

    // for making files before zip
    getCacheDir() {
        const cache_path = path.join(utils.getCacheDir(), 'make_dict_cache');
        if (this.isDebugMode()) {
            return cache_path + '_debug'
        }
        return cache_path;
    }

    // for destination xxxx.fishdict
    getOutputFilePath() {
        if (this.isDebugMode()) {
            return path.join(this.getCacheDir(), 'debug.fishdict');
        }

        let dstPath = this.dst_folder_ ? this.dst_folder_ : utils.getCacheDir()
        dstPath = path.join(dstPath, 'output');
        utils.makeDir(dstPath)
        let fileName = this.options_ && this.options_.dst_name;
        if (!fileName) {
            fileName = this.header_.getItem(dict_config_keys.name)
                + '_'
                + this.header_.getItem(dict_config_keys.version)
                + '.fishdict';
        }
        const fullPath = path.join(dstPath, fileName);
        if (fs.existsSync(fullPath)) {
            utils.rmFile(fullPath)
        }
        return fullPath;
    }

    beforeMake() {
        utils.rmFile(this.getCacheDir())
        utils.makeDir(this.getCacheDir())
    }
}

module.exports = BaseDictMaker;